#target "indesign"

(function () {
    if (!app || app.name !== "Adobe InDesign") {
        alert("请在 Adobe InDesign 中运行此脚本。");
        return;
    }

    if (app.documents.length === 0) {
        alert("请先打开一个文档。");
        return;
    }

    var doc = app.activeDocument;
    var queries = collectGrepQueries();

    if (!queries.length) {
        alert("未找到任何 GREP 预设，请先在“查找/替换”面板中保存至少一个查询。");
        return;
    }

    var userChoice = promptUser(queries, doc);
    if (!userChoice) {
        return;
    }

    var targetPages;
    try {
        targetPages = parsePageRange(doc, userChoice.pageRange);
    } catch (rangeError) {
        alert(rangeError.message);
        return;
    }

    if (!targetPages.length) {
        alert("未匹配到任何页面，请检查输入的页面范围。");
        return;
    }

    var offsets = {};
    for (var i = 0; i < targetPages.length; i++) {
        offsets[targetPages[i].documentOffset] = true;
    }

    var summary;
    try {
        summary = app.doScript(
            runPreset,
            ScriptLanguage.JAVASCRIPT,
            [doc, userChoice.queryName, offsets],
            UndoModes.ENTIRE_SCRIPT,
            "Apply GREP preset to pages"
        );
    } catch (execError) {
        alert("执行 GREP 预设时发生错误：\n" + execError);
        return;
    }

    var totalChanged = summary && typeof summary.changed === "number" ? summary.changed : 0;
    var storyCount = summary && typeof summary.stories === "number" ? summary.stories : 0;
    var frameCount = summary && typeof summary.frames === "number" ? summary.frames : 0;
    var frameHitCount = summary && typeof summary.framesHit === "number" ? summary.framesHit : 0;
    var debugLog = summary && summary.log instanceof Array ? summary.log.slice(0) : [];

    var message =
        "预设：" + userChoice.queryName +
        "\n匹配页面：" + targetPages.length +
        "\n扫描文本框：" + frameCount +
        "\n命中文本框：" + frameHitCount +
        "\n处理故事：" + storyCount +
        "\n替换次数：" + totalChanged;

    if (totalChanged === 0) {
        message += "\n未检测到替换，请确认预设与页面内容是否匹配。";
    }

    if (debugLog.length) {
        var maxItems = 12;
        var lines = debugLog.slice(0, maxItems);
        message += "\n\n调试信息：\n- " + lines.join("\n- ");
        if (debugLog.length > maxItems) {
            message += "\n- ...(其余 " + (debugLog.length - maxItems) + " 条)";
        }
    }

    alert(message);
})();

function runPreset(args) {
    var doc = args[0];
    var queryName = args[1];
    var offsets = args[2];

    var summary = {
        changed: 0,
        stories: 0,
        frames: 0,
        framesHit: 0,
        pages: [],
        log: []
    };

    function log(message) {
        summary.log.push(message);
        try {
            $.writeln("[Page GREP] " + message);
        } catch (_e) {}
    }

    var previousInteraction = app.scriptPreferences.userInteractionLevel;
    var previousRedraw = app.scriptPreferences.enableRedraw;
    var savedFindPrefs = app.findGrepPreferences.properties;
    var savedChangePrefs = app.changeGrepPreferences.properties;
    var savedOptions = app.findChangeGrepOptions.properties;

    try {
        log("开始执行预设：" + queryName);

        app.scriptPreferences.userInteractionLevel = UserInteractionLevels.NEVER_INTERACT;
        app.scriptPreferences.enableRedraw = false;

        app.findGrepPreferences = NothingEnum.NOTHING;
        app.changeGrepPreferences = NothingEnum.NOTHING;

        try {
            app.loadFindChangeQuery(queryName, SearchModes.GREP_SEARCH);
            log(
                "预设载入成功。findWhat=" +
                    describeSetting(app.findGrepPreferences.findWhat) +
                    " | changeTo=" +
                    describeSetting(app.changeGrepPreferences.changeTo)
            );
        } catch (loadError) {
            log("载入预设失败：" + loadError);
            throw loadError;
        }

        var targetOffsets = normalizePageOffsets(doc, offsets);
        summary.pages = targetOffsets.slice(0);
        log("目标页面 offset：" + (targetOffsets.length ? targetOffsets.join(", ") : "(空)"));

        if (!targetOffsets.length) {
            log("无有效目标页面，终止执行。");
            return summary;
        }

        var storyHitMap = {};
        var framesScanned = 0;
        var framesMatched = 0;

        for (var i = 0; i < targetOffsets.length; i++) {
            var offset = targetOffsets[i];
            var page = safeGetPage(doc, offset, log);
            if (!page) {
                continue;
            }

            log("扫描页面：" + page.name + " (offset " + offset + ")");

            var frames = collectTextFrames(page, log);
            if (!frames.length) {
                log("  页面没有文本框。");
                continue;
            }

            for (var f = 0; f < frames.length; f++) {
                var frame = frames[f];
                framesScanned++;

                var frameLabel = describeFrame(frame);
                log("  处理文本框：" + frameLabel);

                if (!frame || !frame.isValid) {
                    log("    文本框无效，跳过。");
                    continue;
                }

                if (frame.locked) {
                    log("    文本框已锁定，跳过。");
                    continue;
                }

                if (!isLayerUsable(frame)) {
                    log("    所在图层隐藏或锁定，跳过。");
                    continue;
                }

                var rangeInfo = getFrameTextRange(frame, log);
                if (!rangeInfo) {
                    log("    无法取得文本范围，跳过。");
                    continue;
                }

                var textRange = rangeInfo.text;
                var story = rangeInfo.story;

                if (story && story.isValid) {
                    storyHitMap[story.id] = true;
                }

                var matchCount = 0;
                try {
                    var matches = textRange.findGrep();
                    matchCount = matches ? matches.length : 0;
                } catch (findError) {
                    log("    findGrep 异常：" + findError);
                    continue;
                }

                if (!matchCount) {
                    log("    未找到匹配。");
                    continue;
                }

                framesMatched++;
                log("    匹配数量：" + matchCount);

                try {
                    var changeResult = textRange.changeGrep();
                    var increment = countChangeResult(changeResult, matchCount);
                    summary.changed += increment;
                    log("    完成替换：" + increment);
                } catch (changeError) {
                    log("    changeGrep 异常：" + changeError);
                }
            }
        }

        summary.frames = framesScanned;
        summary.framesHit = framesMatched;
        summary.stories = countKeys(storyHitMap);

        if (!framesMatched) {
            log("在所选页面上未找到匹配。");
        }
    } finally {
        app.findGrepPreferences = NothingEnum.NOTHING;
        app.changeGrepPreferences = NothingEnum.NOTHING;

        try {
            app.findGrepPreferences.properties = savedFindPrefs;
        } catch (_restoreFind) {}
        try {
            app.changeGrepPreferences.properties = savedChangePrefs;
        } catch (_restoreChange) {}
        try {
            app.findChangeGrepOptions.properties = savedOptions;
        } catch (_restoreOptions) {}

        app.scriptPreferences.userInteractionLevel = previousInteraction;
        app.scriptPreferences.enableRedraw = previousRedraw;
    }

    log("总替换次数：" + summary.changed);
    return summary;
}

function describeSetting(value) {
    if (value === undefined || value === null) {
        return "(未设置)";
    }
    var text = String(value);
    if (!text.length) {
        return "(空)";
    }
    if (text.length > 60) {
        return text.substring(0, 57) + "...";
    }
    return text;
}

function countChangeResult(result, fallback) {
    if (!result && typeof fallback === "number") {
        return fallback;
    }
    if (result instanceof Array) {
        return result.length;
    }
    if (result && typeof result.length === "number") {
        return result.length;
    }
    if (result) {
        return 1;
    }
    return typeof fallback === "number" ? fallback : 0;
}

function normalizePageOffsets(doc, offsetMap) {
    var indices = [];
    if (!doc || !doc.pages || !offsetMap) {
        return indices;
    }
    for (var key in offsetMap) {
        if (!offsetMap.hasOwnProperty(key) || !offsetMap[key]) {
            continue;
        }
        var idx = parseInt(key, 10);
        if (isNaN(idx) || idx < 0 || idx >= doc.pages.length) {
            continue;
        }
        indices.push(idx);
    }
    indices.sort(function (a, b) {
        return a - b;
    });
    return indices;
}

function safeGetPage(doc, offset, logFn) {
    try {
        var page = doc.pages[offset];
        if (page && page.isValid) {
            return page;
        }
    } catch (error) {
        if (logFn) {
            logFn("无法访问页面 offset " + offset + "：" + error);
        }
    }
    return null;
}

function collectTextFrames(page, logFn) {
    if (!page || !page.isValid) {
        return [];
    }
    try {
        var items = page.textFrames.everyItem().getElements();
        if (items === undefined || items === null) {
            return [];
        }
        if (!(items instanceof Array)) {
            return [items];
        }
        return items;
    } catch (error) {
        if (logFn) {
            logFn("  获取文本框失败：" + error);
        }
        return [];
    }
}

function describeFrame(frame) {
    var parts = [];
    try {
            parts.push("ID " + frame.id);
    } catch (_) {}
    try {
        if (frame.label && frame.label.length) {
            parts.push('标签 "' + frame.label + '"');
        }
    } catch (_) {}
    try {
        var page = frame.parentPage;
        if (page && page.isValid && page.name) {
            parts.push("页面 " + page.name);
        }
    } catch (_) {}
    return parts.join(" | ");
}

function isLayerUsable(frame) {
    try {
        var layer = frame.itemLayer;
        if (!layer || !layer.isValid) {
            return true;
        }
        return !!layer.visible && !layer.locked;
    } catch (_) {
        return true;
    }
}

function getFrameTextRange(frame, logFn) {
    if (!frame || !frame.isValid) {
        return null;
    }

    var story = null;
    try {
        story = frame.parentStory;
    } catch (storyError) {
        if (logFn) {
            logFn("    获取故事失败：" + storyError);
        }
    }

    var textRange = null;
    var firstChar = null;
    var lastChar = null;

    try {
        if (frame.characters && frame.characters.length) {
            firstChar = frame.characters.firstItem();
            lastChar = frame.characters.lastItem();
        }
    } catch (charError) {
        if (logFn) {
            logFn("    获取字符范围失败：" + charError);
        }
    }

    if (story && story.isValid && firstChar && lastChar && firstChar.isValid && lastChar.isValid) {
        try {
            textRange = story.characters.itemByRange(firstChar, lastChar);
        } catch (rangeError) {
            if (logFn) {
                logFn("    创建字符范围失败：" + rangeError);
            }
        }
    }

    if ((!textRange || !textRange.isValid) && frame.texts && frame.texts.length) {
        try {
            textRange = frame.texts[0];
        } catch (textError) {
            if (logFn) {
                logFn("    获取 frame.texts 失败：" + textError);
            }
        }
    }

    if (!textRange || !textRange.isValid) {
        return null;
    }

    try {
        if (textRange.characters && textRange.characters.length === 0) {
            return null;
        }
    } catch (_) {}

    return {
        text: textRange,
        story: story,
        label: describeFrame(frame)
    };
}

function countKeys(map) {
    var total = 0;
    for (var key in map) {
        if (map.hasOwnProperty(key)) {
            total++;
        }
    }
    return total;
}

function promptUser(queries, doc) {
    var dialog = new Window("dialog", "批量 GREP 替换（指定页面）");
    dialog.alignChildren = "fill";

    var queryGroup = dialog.add("group");
    queryGroup.add("statictext", undefined, "GREP 预设：");
    var queryDropdown = queryGroup.add("dropdownlist", undefined, queries);
    queryDropdown.minimumSize.width = 240;
    queryDropdown.selection = 0;

    var pageGroup = dialog.add("group");
    pageGroup.add("statictext", undefined, "页面范围：");
    var defaultRange = doc.pages.length
        ? doc.pages[0].name + "-" + doc.pages[doc.pages.length - 1].name
        : "";
    var rangeInput = pageGroup.add("edittext", undefined, defaultRange);
    rangeInput.characters = 30;

    dialog.add("statictext", undefined, "示例：1-3,5,封面-封底,A-1-A-3");

    var buttons = dialog.add("group");
    buttons.alignment = "right";
    buttons.add("button", undefined, "确定", { name: "ok" });
    buttons.add("button", undefined, "取消", { name: "cancel" });

    if (dialog.show() !== 1 || !queryDropdown.selection) {
        return null;
    }

    return {
        queryName: queryDropdown.selection.text,
        pageRange: rangeInput.text
    };
}

function parsePageRange(doc, input) {
    if (!input) {
        throw new Error("页面范围不能为空。");
    }

    var cleaned = String(input).replace(/\s+/g, "");
    if (!cleaned) {
        throw new Error("页面范围不能为空。");
    }

    var tokens = cleaned.split(/[,，;]/);
    var pages = [];
    var seen = {};

    for (var i = 0; i < tokens.length; i++) {
        var token = tokens[i];
        if (!token) {
            continue;
        }

        var expanded = expandToken(token);
        if (!expanded.length) {
            throw new Error("无法解析页面标记：" + token);
        }

        for (var j = 0; j < expanded.length; j++) {
            var page = expanded[j];
            if (!page || !page.isValid) {
                continue;
            }
            var offset = page.documentOffset;
            if (!seen[offset]) {
                seen[offset] = true;
                pages.push(page);
            }
        }
    }

    pages.sort(function (a, b) {
        return a.documentOffset - b.documentOffset;
    });

    return pages;

    function expandToken(token) {
        var direct = resolvePage(token);
        if (direct) {
            return [direct];
        }

        var results = [];
        var hyphenChars = "-\u2010\u2011\u2012\u2013\u2014\u2015";

        for (var idx = 1; idx < token.length - 1; idx++) {
            var ch = token.charAt(idx);
            if (hyphenChars.indexOf(ch) === -1) {
                continue;
            }

            var left = token.substring(0, idx);
            var right = token.substring(idx + 1);
            if (!left || !right) {
                continue;
            }

            var startPage = resolvePage(left);
            var endPage = resolvePage(right);
            if (!startPage || !endPage) {
                continue;
            }

            var startIdx = startPage.documentOffset;
            var endIdx = endPage.documentOffset;
            var step = startIdx <= endIdx ? 1 : -1;

            for (var pos = startIdx; pos !== endIdx + step; pos += step) {
                results.push(doc.pages[pos]);
            }
            break;
        }

        return results;
    }

    function resolvePage(rawName) {
        if (!rawName) {
            return null;
        }

        var name = String(rawName);

        try {
            var page = doc.pages.itemByName(name);
            if (page && page.isValid) {
                return page;
            }
        } catch (_) {}

        var lower = name.toLowerCase();
        for (var i = 0; i < doc.pages.length; i++) {
            var candidate = doc.pages[i];
            if (candidate.name && candidate.name.toLowerCase() === lower) {
                return candidate;
            }
        }

        if (/^\d+$/.test(name)) {
            var index = parseInt(name, 10) - 1;
            if (index >= 0 && index < doc.pages.length) {
                return doc.pages[index];
            }
        }

        return null;
    }
}

function collectGrepQueries() {
    var names = [];
    var seen = {};
    var folders = getQueryFolders();

    for (var i = 0; i < folders.length; i++) {
        var folder = folders[i];
        if (!folder || !folder.exists) {
            continue;
        }

        try {
            var files = folder.getFiles(function (item) {
                return item instanceof File && /\.xml$/i.test(item.name);
            });

            for (var j = 0; j < files.length; j++) {
                var name = files[j].displayName.replace(/\.xml$/i, "");
                if (!seen[name]) {
                    seen[name] = true;
                    names.push(name);
                }
            }
        } catch (_) {}
    }

    names.sort(function (a, b) {
        var left = a.toLowerCase();
        var right = b.toLowerCase();
        if (left < right) {
            return -1;
        }
        if (left > right) {
            return 1;
        }
        return 0;
    });

    return names;
}

function getQueryFolders() {
    var results = [];
    var appBases = [];

    if (Folder.appPackage && Folder.appPackage.exists) {
        appBases.push(Folder.appPackage);
    }
    if (app.filePath && app.filePath.exists) {
        appBases.push(app.filePath);
    }

    for (var i = 0; i < appBases.length; i++) {
        var base = appBases[i];
        maybePushFolder(results, new Folder(base.fullName + "/Presets/Find-Change Queries/GREP"));
        maybePushFolder(results, new Folder(base.fullName + "/Resources/Find-Change Queries/GREP"));
    }

    var userRoot = new Folder(Folder.userData.fullName + "/Adobe/InDesign");
    if (userRoot.exists) {
        var versionFolders = userRoot.getFiles(function (item) {
            return item instanceof Folder;
        });

        for (var v = 0; v < versionFolders.length; v++) {
            var versionFolder = versionFolders[v];
            addQueryFolder(results, versionFolder);

            var localeFolders = versionFolder.getFiles(function (child) {
                return child instanceof Folder;
            });

            for (var l = 0; l < localeFolders.length; l++) {
                addQueryFolder(results, localeFolders[l]);
            }
        }
    }

    return results;
}

function addQueryFolder(list, baseFolder) {
    if (!baseFolder) {
        return;
    }
    maybePushFolder(list, new Folder(baseFolder.fsName + "/Find-Change Queries/GREP"));
}

function maybePushFolder(list, folder) {
    if (!folder || !folder.exists) {
        return;
    }
    var path = folder.fsName;
    for (var i = 0; i < list.length; i++) {
        if (list[i].fsName === path) {
            return;
        }
    }
    list.push(folder);
}
